import math
from util import *
import random
import numpy as np
import pickle
from Method.GA_for_init_ms_deploy import GA_algorithm

class HEFT:
    #没有服务删除和部署的过程，一开始就进行初始化部署，然后后续不进行改变。
    def __init__(self, cloud_system):
        self.cloud_system=cloud_system
        self.env=cloud_system.env
        return
    
    def init_deploy(self):
        
        init_deploy_strategy_file_path=self.cloud_system.args.history_trace_file+f"_{self.cloud_system.args.bare_metal_node_num}.GA_init_deploy"
        if os.path.exists(init_deploy_strategy_file_path):
            with open(init_deploy_strategy_file_path, "rb") as file:
                storage_data=pickle.load(file)
                spec_ms_kind_list=storage_data["ms_kind_list"]
                spec_node_index_list=storage_data["node_index_list"]
                self.cloud_system.ms_kind_to_resource_need=storage_data["resource_cost"]
                self.execute_init_deploy(spec_ms_kind_list, spec_node_index_list)
        else:
            if self.cloud_system.print_level>1:
                print("get init deploy strategy...")
            system_ms_kind_list=self.cloud_system.ms_kind_list
            weight_matrix_t = self.get_weight_matrix(system_ms_kind_list)
            cpu_cost_t = []
            mem_cost_t = []
            disk_cost_t = []
            for ms_kind in system_ms_kind_list:
                cpu_cost_t.append(self.cloud_system.ms_kind_to_resource_need[ms_kind]["cpu"])
                mem_cost_t.append(self.cloud_system.ms_kind_to_resource_need[ms_kind]["mem"])
                disk_cost_t.append(self.cloud_system.ms_kind_to_resource_need[ms_kind]["disk"])
            cpu_cost_t = np.array(cpu_cost_t)
            mem_cost_t = np.array(mem_cost_t)
            disk_cost_t = np.array(disk_cost_t)
            ms_ratio_t = np.sum(weight_matrix_t, axis=1) / np.sum(weight_matrix_t)
            node_num_t = self.cloud_system.args.bare_metal_node_num
            print("start GA...")
            best_pop=GA_algorithm(weight_matrix_t, cpu_cost_t, mem_cost_t, disk_cost_t, ms_ratio_t, node_num_t)
            
            #获取ms_kind_list， node_index_list
            spec_ms_kind_list=[]
            spec_node_index_list=[]
            for node_index in range(best_pop.shape[0]):
                for ms_kind_index in range(best_pop.shape[1]):
                    ms_number=best_pop[node_index][ms_kind_index]
                    for _ in range(ms_number):
                        spec_ms_kind_list.append(system_ms_kind_list[ms_kind_index])
                        spec_node_index_list.append(node_index)
                        
            with open(init_deploy_strategy_file_path, "wb") as file:
                storage_data={
                    "ms_kind_list":spec_ms_kind_list,
                    "node_index_list":spec_node_index_list,
                    "resource_cost":self.cloud_system.ms_kind_to_resource_need
                }
                pickle.dump(storage_data, file)
            print("end init strategy!")
            self.execute_init_deploy(spec_ms_kind_list, spec_node_index_list)
            
            return
        
    def get_weight_matrix(self, system_ms_kind_list):
        #基于类型，统计邻接矩阵
        ms_kind_adj=np.zeros((len(system_ms_kind_list), len(system_ms_kind_list)))
        for service_name in self.cloud_system.service_to_callgraph_dict.keys():
            request_t=self.cloud_system.service_to_callgraph_dict[service_name]
            for task_t in request_t.start_task_list:
                self.get_weight_matrix_update(task_t, ms_kind_adj, system_ms_kind_list)
        return ms_kind_adj
        
    def get_weight_matrix_update(self,task, ms_kind_adj, system_ms_kind_list):
        for sub_task in task.sub_task_list:
            task_index=system_ms_kind_list.index(task.ms_kind)
            sub_task_index=system_ms_kind_list.index(sub_task.ms_kind)
            call_num=task.call_num
            ms_kind_adj[task_index][sub_task_index]+=call_num
            ms_kind_adj[sub_task_index][task_index]+=call_num
            self.get_weight_matrix_update(sub_task, ms_kind_adj, system_ms_kind_list)
        


    def get_adj_kind_matrix(self, each_ms_kind_deploy_number):
        #基于类型，统计邻接矩阵
        ms_kind_list=self.cloud_system.ms_kind_list
        ms_kind_adj=np.zeros((len(ms_kind_list), len(ms_kind_list)))
        for service_name in self.cloud_system.service_to_callgraph_dict.keys():
            request_t=self.cloud_system.service_to_callgraph_dict[service_name]
            for task_t in request_t.start_task_list:
                self.get_adj_matrix_update_ms_kind_adj(task_t, ms_kind_adj,ms_kind_list)
        #基于类型统计的邻接矩阵，计算计划生成的微服务call 邻接矩阵
        ms_list=[]
        for ms_kind in ms_kind_list:
            for order in range(each_ms_kind_deploy_number):
                ms_list.append(ms_kind)
                
        ms_adj=np.zeros((len(ms_list), len(ms_list)))
        for i in range(len(ms_list)):
            for j in range(0, i):
                pre_index_i=math.floor(i/each_ms_kind_deploy_number)
                pre_index_j=math.floor(j/each_ms_kind_deploy_number)
                call_num=ms_kind_adj[pre_index_i][pre_index_j]
                ms_adj[i][j]=call_num
                ms_adj[j][i]=call_num
        return ms_adj, ms_list
    
    
    
    def execute_init_deploy(self, ms_kind_list, node_index_list):
        #这里输入的ms_kind_list和cloud system 中的不一样，这里的可以重复，而cloud system中的不可以重复
        self.ms_id_list_exist=[]
        self.ms_kind_list_exist=[]
        self.ms_node_index_exist=[]
        self.ms_kind_to_number={}
        
        #根据社团划分结果进行部署
        for index in range(len(ms_kind_list)):
            ms_kind=ms_kind_list[index]
            node_index=node_index_list[index]
            result, ms_obj= self.cloud_system.bare_metal_node_list[node_index].deploy_ms(ms_kind, without_cold_start=True)
            assert result==True
                    
            self.ms_id_list_exist.append(ms_obj.ms_id)
            self.ms_kind_list_exist.append(ms_kind)
            self.ms_node_index_exist.append(node_index)
            if ms_kind not in self.ms_kind_to_number.keys():
                self.ms_kind_to_number[ms_kind]=1
            else:
                self.ms_kind_to_number[ms_kind]+=1
            
            self.set_ms_obj_end_time(ms_obj, 0)
        
        self.ms_call_matrix=np.zeros((len(self.ms_id_list_exist), len(self.ms_id_list_exist)))
    
    def set_ms_obj_end_time(self, ms_obj, end_time):
        self.ms_obj_to_end_time[ms_obj]=end_time
        return
    
#************************************************************************************************************    
    #调度入口函数
    def do_schedule_deploy(self):
        
        time_now=self.cloud_system.env.now
        for task in self.cloud_system.pending_task_queue:
            base_request=self.cloud_system.service_to_callgraph_dict[task.request.service_name]
            base_task=base_request.task_id_to_obj[task.task_id]
            task.ave_deal_time=base_task.ave_deal_time
            task.set_replica_num(1)
            #获取所有正在运行的微服务列表
            ms_obj_list=self.cloud_system.get_available_ms_obj_with_ms_kind(task.ms_kind)
            ##统计每个类型的微服务队列长度信息
            ms_obj_with_early_start_time_list=[]
            for ms_obj in ms_obj_list:
                #更新最早开始时间
                ms_idle_time=ms_obj.get_estimate_end_time()   #都是绝对时间
                TT=self.cloud_system.get_TT_time(task, ms_obj, task.first_parent_ms_obj)
                early_start_time=max(time_now+TT, ms_idle_time)
                ms_obj_with_early_start_time_list.append([early_start_time, ms_obj])
            
            assert len(ms_obj_with_early_start_time_list)>0
            ms_obj_with_early_start_time_list.sort(key=lambda x:x[0])
            [early_start_time, ms_obj_new]=ms_obj_with_early_start_time_list[0]
            self.cloud_system.env.process(ms_obj_new.put_task_to_queue_event(task))
            task.aim_ms_obj_list.append(ms_obj_new)
            
        #清空队列
        self.cloud_system.pending_task_queue=[]
        return
        
    

